#include "GLDebugger.hpp"
#include <iostream>

namespace Debug {

#define GLSourceToSource(s)       (static_cast<GLDebugSources>((s & 0xF) - 0x6))
#define GLTypeToType(t)           (static_cast<GLDebugTypes>(((t & 0xFF) - 0xC) & 0xF))
#define GLServerityToServerity(s) (static_cast<MessageSeverity>((s & 0xF) - 0x6))

void MessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
                     GLsizei length, const GLchar *message, const void *userParam) {
  static Debugger debugger;
  debugger.Log({source, type, id, severity, message});
}

std::string GetGLDebugSourceString(GLDebugSources source) {
  static std::string table[] = {
      "From OGL API",       "From Window API", "From Shader Compiler",
      "From App about OGL", "From App",        "From Other",
  };

  return table[static_cast<size_t>(source)];
}

std::string GetGLDebugTypeString(GLDebugTypes type) {
  static std::string table[] = {"Error",       "Deperated",   "Undefined",
                                "NotPortable", "Performance", "Other"};
  return table[static_cast<size_t>(type)];
}

std::string GetGLDebugServerityString(MessageSeverity serverity) {
  static std::string table[] = {"error", "warn", "info", "hint"};
  if (serverity >= MessageSeverity::Count)
    return table[MessageSeverity::hint];
  return table[static_cast<size_t>(serverity)];
}

GLenum GetGLDebugSource(GLDebugSources source) {
  static GLenum table[] = {
      GL_DEBUG_SOURCE_API,
      GL_DEBUG_SOURCE_WINDOW_SYSTEM,
      GL_DEBUG_SOURCE_SHADER_COMPILER,
      GL_DEBUG_SOURCE_THIRD_PARTY,
      GL_DEBUG_SOURCE_APPLICATION,
      GL_DEBUG_SOURCE_OTHER,
  };
  return table[static_cast<size_t>(source)];
}

GLenum GetGLDebugType(GLDebugTypes type) {
  static GLenum table[] = {GL_DEBUG_TYPE_ERROR,
                           GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR,
                           GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR,
                           GL_DEBUG_TYPE_PORTABILITY,
                           GL_DEBUG_TYPE_PERFORMANCE,
                           GL_DEBUG_TYPE_OTHER};
  return table[static_cast<size_t>(type)];
}

void Debugger::Log(const GLMessageParams &params) { GLMessageFiltering(params); }

void Debugger::GLMessageFiltering(const GLMessageParams &params) {

  auto source = GLSourceToSource(params.source);
  auto type = GLTypeToType(params.type);
  auto serverity = GLServerityToServerity(params.serverity);
  if (serverity >= MessageSeverity::hint) {
    return;
  }
  // source handle
  auto sourceStr = GetGLDebugSourceString(source);
  auto typeStr = GetGLDebugTypeString(type);
  auto serverityStr = GetGLDebugServerityString(serverity);

  if (_messageStream.count(params.message) == 0) {
    _messageStream.insert(params.message);
    std::cerr << '[' << serverityStr << "]: " << params.message << " -> " << sourceStr
              << std::endl;
  }
} // namespace Debug

} // namespace Debug